'''
Created on Feb 22, 2012

@author: daoxuandung
'''

"""
Use ndb (new database) to create all models

For UserProperty, to use webapp2_extras, now will be replaced by
ndb.KeyProperty(kind=User)

Objects are normalized to minimize any object is changed too much
"""
from google.appengine.ext import ndb
from google.appengine.ext.ndb import polymodel
from webapp2_extras.appengine.auth.models import User
from web import settings
import logging

class Feed(ndb.Model):
    """
    Application must filter Feed objects depends on current user.
    Feed represents the story to put on User page. A typical story is:
    'Who is doing what with whom, at where'
    Many object models can appear on NewsFeed:
    - Post: User A posted on Event wall
    - Event: User A created a new Event
    - Photo: User A added 28 new photos to album Mobile Upload of Event A
    - Invite: User A invites user B, user C to go for event A
    
    This Feed is built very similar to Facebook Feed, but focus on event
    
    "User A": actor, "added": action, "28 new photos": photos, 
    "album Mobile Upload": target
    
    How Feed is created: using prehook or posthook after model is saved
    
    A Feed might not be commented on. If Feed contains Post, 
    NewsFeed should show Post View with comments
    
    A Feed has a type - for easier categorization
    """
    actor = ndb.KeyProperty(kind=User)
    privacy = ndb.StringProperty()
    event_category = ndb.ComputedProperty(lambda self: self.event.get().category \
                                          if self.event.get().category else None)
    photo = ndb.KeyProperty(kind='Photo')
    post = ndb.KeyProperty(kind='Post')
    event = ndb.KeyProperty(kind='Event')
    feed_type = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    

class Theme(ndb.Expando):
    """
    Theme belongs to a event. It requires name and a custom number of parameter
    """
    name = ndb.StringProperty(required=True)

class Category(ndb.Model):
    """
    Each Event belongs to one or more Category
    E.g: Business, Music, Movie, Conference, etc
    Refer to AppStore for more
    """
    category = ndb.StringProperty()
    
class Event(ndb.Model):
    """
    An Event is created by a event organizer (a User). 
    Some basic info is: name,
    description, organizer info, venue, start time, end time, slogan
    An Event can have one or multiple Category
    Event can have 0 or multiple tickets, number of tickets
    Event can have 0 or multiple RSVP
    Event can have 0 or multiple Post
    Event can have 0 or multiple Note
    Event can be public or closed
    Event can have one or more Interest Tag
    Event can have one or more Feedback
    Event can have one or more Poll (Useful for polling)
    Event page can have it own themes. Event might have one or more Album attached
    Event can have one or more files attached. (Useful for class training event)
    Event can have one or more comments
    """
    organizer = ndb.KeyProperty(kind=User)
    published = ndb.BooleanProperty(default=False)
    popularity = ndb.FloatProperty()
    name = ndb.StringProperty()
    slogan = ndb.StringProperty()
    category = ndb.KeyProperty(kind="Category", repeated=True)
    description = ndb.TextProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    start_time = ndb.DateTimeProperty()
    end_time = ndb.DateTimeProperty()
    number_of_tickets = ndb.IntegerProperty()
    privacy = ndb.StringProperty(choices=[settings.EVENT_PRIVACY_PUBLIC, 
                                          settings.EVENT_PRIVACY_PRIVATE])
    theme = ndb.KeyProperty(kind='Theme')
    is_new = ndb.BooleanProperty(default=True, indexed=False) # use internally, not for query
    tickets = ndb.KeyProperty(kind='Ticket', repeated=True)
    locations = ndb.KeyProperty(kind='Location', repeated=True)
    locations_titles = ndb.StringProperty(repeated=True)
    cover_album = ndb.KeyProperty(kind='Album')
    default_album = ndb.KeyProperty(kind='Album')
    
    def _post_put_hook(self, future):
        # This only triggered first time
        if self.is_new:
            # Create feed
            feed = Feed()
            feed.actor = self.organizer
            feed.event = self.key
            feed.feed_type = "EVENT"
            logging.info(feed.event)
            
            # Create album
            cover_album = Album()
            cover_album.event = self.key
            cover_album.author = self.organizer
            cover_album.title = "Cover Album"
                
            # Create default album
            default_album = Album()
            default_album.event = self.key
            default_album.author = self.organizer
            default_album.title = "Default Album"
            
            keys = ndb.put_multi([feed, cover_album, default_album])
            self.cover_album = keys[1]
            self.default_album = keys[2]      
            self.is_new = False
            self.put()


class Comment(ndb.Model):
    """
    A comment belong to a user. User can delete a comment, but can NOT edit
    """
    comment = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    author = ndb.KeyProperty(kind=User)
    commentable = ndb.KeyProperty()
    with_tags = ndb.KeyProperty(kind=User, repeated=True)
    

class Tag(ndb.Model):
    """
    To allow custom string tag. This is similar to hashtag of Twitter, does not
    have to be user
    """
    tag = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)

    
class Like(polymodel.PolyModel):
    """
    Store what User like
    
    Follow closely to Facebook Like structure, e.g
    {
        "name": "OCBC Careers",
        "category": "Bank/financial institution",
        "id": "118573398198976",
        "created_time": "2012-03-22T07:35:04+0000"
    }

    """
    name = ndb.StringProperty()
    category = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    

class Location(ndb.Model):
    """
    Location is for user to check in. Location can belong to a Post, Note or Comment
    """
    city = ndb.StringProperty()
    country = ndb.StringProperty()
    latitude = ndb.FloatProperty()
    longitude = ndb.FloatProperty()
    postal_code = ndb.StringProperty()
    state = ndb.StringProperty()
    address = ndb.StringProperty()


class CheckIn(ndb.Model):
    """
    An User can create a CheckIn at a specific Location
    A CheckIn can have one or more Comment
    """
    location = ndb.KeyProperty(kind='Location')
    author = ndb.KeyProperty(kind=User)
    created_time = ndb.DateTimeProperty(auto_now_add=True)    
    
    
class Album(ndb.Model):
    """
    An album consists of one or many photos, each photo has its sequence
    
    An album belongs to a user. It might have one or more comments
    An album has a title and description
    An album can have one or more Location
    """
    author = ndb.KeyProperty(kind=User)
    title = ndb.StringProperty()
    description = ndb.StringProperty()
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class Photo(ndb.Model):
    """
    An photo normally belongs to an user. It is possible to have 
    an photo without user, however it will be deleted after some
    configurable duration
    
    An photo belongs to an album. If no album parameter is passed,
    photo will belong to a default album (like Facebook Mobile Uploads)
    
    An photo can have one or more comments
    An photo can have one Location
    A photo can be star, and display on the main event page
    """
    author = ndb.KeyProperty(kind=User)
    blob_key = ndb.BlobKeyProperty()
    url = ndb.StringProperty(indexed=False)
    description = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    location = ndb.KeyProperty(kind='Location')
    with_tags = ndb.KeyProperty(kind=User, repeated=True)
    album = ndb.KeyProperty(kind='Album')
    star = ndb.BooleanProperty()
    event = ndb.KeyProperty(kind='Event')
    
    def _post_put_hook(self, future):
        # Create feed
        feed = Feed()
        feed.actor = self.author
        feed.photo = self.key
        feed.event = self.event
        feed.feed_type = "PHOTO"
        feed.put()
    

class AlbumLocation(ndb.Model):
    """
    Store all location of the album
    """
    location = ndb.KeyProperty(kind='Location')
    album = ndb.KeyProperty(kind='Album')
    
    
class PhotoLocation(ndb.Model):
    location = ndb.KeyProperty(kind='Location')
    photo = ndb.KeyProperty(kind='Photo')


class Post(ndb.Model):
    
    """
    A Post is showed on main page of event. A post can have one or many comments
    A Post is created by user. It is possible to delete a post, but should NOT edit
    A Post can be star or normal post. A star Post will be on top of all the post
    """
    post = ndb.StringProperty()
    event = ndb.KeyProperty(kind='Event')
    star = ndb.BooleanProperty(default=False)
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    author = ndb.KeyProperty(kind=User)
    with_tags = ndb.KeyProperty(kind=User, repeated=True)
    location = ndb.KeyProperty(kind='Location')
    photos = ndb.KeyProperty(kind='Photo', repeated=True)
    def _post_put_hook(self, future):
        # Create feed
        feed = Feed()
        feed.actor = self.author
        if self.event.kind() == "Event":
            feed.event = self.event
        feed.post = self.key
        feed.feed_type = "POST"
        feed.participants = self.with_tags
        feed.put()
    

class Note(ndb.Model):
    """
    A Note is long document to describe about an event. Note belongs to User and is editable
    A Note can have one or more Comment
    """
    note = ndb.TextProperty()
    author = ndb.KeyProperty(kind=User)
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class Interest(ndb.Model):
    """
    Each User might have one or more interest
    Each Event can tag one or more interest
    """
    interest = ndb.StringProperty()


class UserInterest(ndb.Model):
    """
    Model interest for each user
    """
    interest = ndb.KeyProperty(kind='Interest')
    author = ndb.KeyProperty(kind=User)
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    
    
class Poll(ndb.Model):
    """
    A Poll is created by user to a specific event. It may have one or multiple option.
    A Poll might belong to a survey or stay independent
    """
    author =  ndb.KeyProperty(kind=User)
    event = ndb.KeyProperty(kind='Event')
    question = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class Option(ndb.Model):
    """
    An User creates an Option to a specific Poll. Option contains all possible contents
    to a Poll
    An Option can be selected by many users
    """
    author =  ndb.KeyProperty(kind=User)
    option = ndb.StringProperty()
    poll = ndb.KeyProperty(kind='Poll')
    users = ndb.KeyProperty(kind=User, repeated=True)
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    

class EventUserView(ndb.Model):
    """
    For statistics: show User viewed an Event at certain time
    Useful for use cases "How many people are viewing this event" or
    "Your friends have viewed this event"
    """
    user = ndb.KeyProperty(kind=User)
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)
        
    
class EventInterest(ndb.Model):
    """
    EventInterest maps Interest and Event
    It help minimize race condition: many process try to modify Event in same time
    """
    interest = ndb.KeyProperty(kind='Interest')
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)

    
class EventLocation(ndb.Model):
    """
    Event might have one or more location
    """
    location = ndb.KeyProperty(kind='Location')
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class EventPoll(ndb.Model):
    """
    EventPoll maps Poll and Event
    It help minimize race condition: many process try to modify Event in same time
    """
    poll = ndb.KeyProperty(kind='Poll')
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    

class EventAlbum(ndb.Model):
    """
    EventAlbum maps Album and Event
    It help minimize race condition: many process try to modify Event in same time
    """
    album = ndb.KeyProperty(kind='Album')
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class EventFile(ndb.Model):
    """
    EventFile maps File and Event
    It help minimize race condition: many process try to modify Event in same time
    """
    blob_key = ndb.BlobKeyProperty()
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)

    
class EventTag(ndb.Model):
    """
    Define tags for an Event
    """
    tag = ndb.KeyProperty(kind='Tag')
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class EventInvite(ndb.Model):
    """
    Allow user to invite friends to an event. Any user can invite,
    not restrict to only organizer
    """
    inviter = ndb.KeyProperty(kind=User)
    message = ndb.StringProperty()
    invitees = ndb.KeyProperty(kind=User, repeated=True)
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    
    def _post_put_hook(self, future):
        # Create feed
        feed = Feed()
        feed.actor = self.inviter
        feed.event = self.event
        feed.participants = self.invitees
        feed.feed_type = "INVITE"
        feed.put()

class RSVP(ndb.Model):
    """
    A RSVP states how user responds to Event. Possible status "Pending", 
    "May be", "Confirmed", "Declined"
    
    Status Values: 
        - "Maybe"  :   User has chosen Maybe to participate
        - "Confirmed": User has confirmed attendance
        - "Declined":  User has rejected the invitation
    """
    invitee = ndb.KeyProperty(kind=User)
    status = ndb.StringProperty(choices=[settings.RSVP_CONFIRMED, 
                                         settings.RSVP_DECLINED, 
                                         settings.RSVP_PENDING]) 
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)

    
class Ticket(ndb.Model):
    """
    A Ticket belongs to one event
    Ticket can have price, event details
    """
    category = ndb.StringProperty()
    detail = ndb.TextProperty()
    price = ndb.FloatProperty()
    quantity = ndb.IntegerProperty()
    photo_url = ndb.StringProperty()
    paypal_key = ndb.StringProperty()
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class EventOffer(ndb.Model):
    """
    Allow event organizer to create offer to user
    Offer has name, fix value off or percent off, valid time, quantity
    """
    offer = ndb.StringProperty()
    fix_value_off = ndb.FloatProperty()
    percent_off = ndb.FloatProperty()
    start_time = ndb.DateTimeProperty()
    end_time = ndb.DateTimeProperty()
    quantity = ndb.IntegerProperty()
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class Rating(ndb.Model):
    """
    A User creates a Feedback about an Event
    Feedback may include rating, suggestion, acknowledged by even organizer
    Feedback may have comment
    A quick way to provide Feedback is to mark it as "recommend"
    """
    author =  ndb.KeyProperty(kind=User)
    rating = ndb.IntegerProperty()
    event = ndb.KeyProperty(kind='Event')
    created_time = ndb.DateTimeProperty(auto_now_add=True)

  
class UserEventList(ndb.Model):
    """
    Allow User to add an Event to his own list
    For example, he can create WatchLater list, Music list, Sport list
    """
    user = ndb.KeyProperty(kind=User)
    event = ndb.KeyProperty(kind='Event')
    list_type = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)

  
class Group(ndb.Model):
    """
    An User can belong to a Group
    A Group can contains one to many User
    A Group, by default is private only to Group members
    """
    name = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    private = ndb.BooleanProperty(default=True)
    participants = ndb.KeyProperty(kind=User, repeated=True)


class Conversation(ndb.Model):
    """
    A Conversation has many messages
    A Conversation is between multiple Users
    """
    subject = ndb.StringProperty()
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    participants = ndb.KeyProperty(kind=User, repeated=True)


class Message(ndb.Model):
    """
    A Message is sent from a User to a Group
    In special case, a Group might only have 2 Users
    """
    author = ndb.KeyProperty(kind=User)
    message = ndb.StringProperty()
    conversation = ndb.KeyProperty(kind='Conversation')
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    

class UserTicket(ndb.Model):
    """
    A UserTicket specifies the ticket user has bought
    """
    ticket = ndb.KeyProperty(kind='Ticket')
    buyer = ndb.KeyProperty(kind=User)
    created_time = ndb.DateTimeProperty(auto_now_add=True)


class UserOffer(ndb.Model):
    """
    Allow User to claim an offer
    """
    author = ndb.KeyProperty(kind=User)
    offer = ndb.KeyProperty(kind='EventOffer')
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    
        
class Follow(ndb.Model):
    """
    Allow an User to follow another User
    """
    follower = ndb.KeyProperty(kind=User)
    followee = ndb.KeyProperty(kind=User)
    created_time = ndb.DateTimeProperty(auto_now_add=True)
    

class Friend(ndb.Model):
    """
    Model Friend
    """
    pair = ndb.KeyProperty(kind=User, repeated=True)


class FacebookFriend(Friend):
    """
    Model Facebook Friend
    """
    pass


class GoogleFriend(Friend):
    """
    Model Google Friend
    """
    pass


class YahooFriend(Friend):
    """
    Model Yahoo Friend
    """
    pass